package smallfrench.moteur.noeuds;

import java.util.ArrayList;

import smallfrench.moteur.biblio.RClasse;
import smallfrench.moteur.biblio.RRien;
import smallfrench.moteur.biblio.err.ErreurClasse;
import smallfrench.moteur.biblio.err.ErreurRien;
import smallfrench.moteur.biblio.err.ErreurVariableIndefinie;
import smallfrench.moteur.interp.Portee;
import smallfrench.moteur.interp.RObjet;
import smallfrench.moteur.interp.err.RException;
import smallfrench.moteur.lexer.lexemes.Lexeme;

/**
 * Appel de fonction (ou de constructeur).
 * @author antoine1023
 */
public class NAppelFonction extends Noeud {

	/**
	 * Noeuds des paramètres.
	 */
	private final Noeud[] noeudsParametres;

	/**
	 * Tout noeud retournant dont la fonction {@code evaluer()}
	 * retourne une fonction.
	 */
	private final Noeud noeudFonction;

	/**
	 * 
	 * @param token Le token de la parenthèse ouvrante
	 * @param fonction
	 * @param parametres
	 */
	public NAppelFonction(Lexeme token, Noeud fonction,
			ArrayList<Noeud> parametres) {

		super(token);
		noeudFonction = fonction;
		noeudsParametres = parametres.toArray(
				new Noeud[parametres.size()]);
	}

	private RObjet[] evaluerParams(Portee portee) {
		RObjet[] r = new RObjet[noeudsParametres.length];
		for (int i = 0 ; i < r.length ; i++) {
			r[i] = noeudsParametres[i].evaluer(portee);
		}
		return r;
	}

	/**
	 * Lorsqu'on appelle une méthode d'une superclasse.
	 * 
	 * @param npp
	 * @param portee
	 * @return La valeur de retour de la méthode
	 */
	private RObjet appelPointPoint(NPointPoint npp, Portee portee) {

		RObjet moi = portee.getMoi();

		RObjet o = npp.noeudClasse.evaluer(portee);
		if (! (o instanceof RClasse))
			new ErreurClasse(o, "classe");

		RClasse classe = (RClasse) o;

		RObjet methode;
		if (npp.nom.equals("constructeur")) {
			methode = classe;
		} else {
			methode = classe.getMethode(npp.nom);
			new ErreurVariableIndefinie(npp.nom).lancer();
		}

		return methode.appeler(moi, evaluerParams(portee));
	}

	@Override
	public RObjet evaluer(Portee portee) {

		try {
			if (noeudFonction instanceof NPointPoint) {

				return appelPointPoint((NPointPoint) noeudFonction, portee);

			} else if (noeudFonction instanceof NPoint) {

				// On appelle une méthode de classe

				NPoint noeudPoint = (NPoint)noeudFonction;
				RObjet objet = noeudPoint.noeudObjet.evaluer(portee);
				RObjet ofn;

				if (objet == RRien.RIEN)
					throw ErreurRien.nouvelleException();

				ofn = objet.getMembre(noeudPoint.nom);

				if (ofn == null) {
					ErreurVariableIndefinie e = new ErreurVariableIndefinie(
							noeudPoint.nom);

					throw new RException(e);
				}

				return ofn.appeler(objet, evaluerParams(portee));
			}

			RObjet c = noeudFonction.evaluer(portee);

			if (c == RRien.RIEN)
				throw ErreurRien.nouvelleException();

			return c.appeler(RRien.RIEN, evaluerParams(portee));

		} catch (RException re) {
			re.empilerPosition(lexeme.fin);
			throw re;
		}

	}

	@Override
	public String toString() {

		String s = noeudFonction +  "(";

		for (Noeud i : noeudsParametres) {
			s += i + " ";
		}
		s += ")";
		return s;
	}

}
